#include "win32/window.h"
#include "win32/lean_windows.h"
#include "win32/throw_win32_exception.h"

#include "common/assert.h"

// For ChooseFont
#include <commdlg.h>

#include <map>

bool Window::s_window_class_created = false;
std::map<HWND, Window*> Window::s_tracked_windows;
//HINSTANCE Window::s_hinstance = 0;
const char* Window::s_class_name = 0;
int Window::s_timer_id_counter = 0;

LRESULT CALLBACK Window::wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	if (s_tracked_windows.find(hwnd) == s_tracked_windows.end())
	{
		// Not for me
		return DefWindowProc(hwnd, message, wparam, lparam);
	}
	
	Window* window = s_tracked_windows[hwnd];
	Message_Types types = window->my_message_types();

	switch (message)
	{
	case WM_CLOSE:
		PostQuitMessage(0);
		return 0;
	default:
		break;
	}

	if (types & mt_timer)
	{
		if (message == WM_TIMER)
		{
			window->on_timer();
			return 0;
		}
	}
	
	if (types & mt_paint)
		switch (message)
		{
		case WM_PAINT:
			{
				PAINTSTRUCT ps;
				BeginPaint(hwnd, &ps);

				HBRUSH brush = CreateSolidBrush(window->bg_color());
				HBRUSH old_brush = (HBRUSH) SelectObject(ps.hdc, brush);
					
				HPEN pen = CreatePen(PS_SOLID, 0, window->fg_color());
				HPEN old_pen = (HPEN) SelectObject(ps.hdc, pen);

				HFONT old_font = (HFONT) SelectObject(ps.hdc, window->m_font);
				
				window->on_paint(ps, pen, brush);

				SelectObject(ps.hdc, old_font);

				DeleteObject(SelectObject(ps.hdc, old_pen));
				DeleteObject(SelectObject(ps.hdc, old_brush));
				
				EndPaint(hwnd, &ps);

				// Required to validate the update region!
//				return DefWindowProcA(hwnd, message, wparam, lparam);
				return 0;
			}
		
		case WM_ERASEBKGND:
			return 0;
			
		default:
			break;
		}

	if (types & mt_mouse)
	{
		MouseButton button;
		switch (message)
		{
		case WM_LBUTTONDOWN:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDOWN:
			button =
				message == WM_LBUTTONDOWN ? mb_left :
				message == WM_MBUTTONDOWN ? mb_mid :
				/*message == WM_RBUTTONDOWN ?*/ mb_right;
				
			window->on_mouse_down(button, LOWORD(lparam), HIWORD(lparam));
			return 0;

		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
			button =
				message == WM_LBUTTONUP ? mb_left :
				message == WM_MBUTTONUP ? mb_mid :
				/*message == WM_RBUTTONUP ?*/ mb_right;

			window->on_mouse_up(button, LOWORD(lparam), HIWORD(lparam));
			return 0;

		case WM_LBUTTONDBLCLK:
		case WM_MBUTTONDBLCLK:
		case WM_RBUTTONDBLCLK:
			button =
				message == WM_LBUTTONDBLCLK ? mb_left :
				message == WM_MBUTTONDBLCLK ? mb_mid :
				/*message == WM_RBUTTONDBLCLK ?*/ mb_right;
			window->on_mouse_doubleclick(button, LOWORD(lparam), HIWORD(lparam));
			return 0;
			
		case WM_MOUSEMOVE:
			window->on_mouse_move(LOWORD(lparam), HIWORD(lparam));
			return 0;
		}
	}
		
	if (types & mt_keyboard)
		switch (message)
		{
		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
			{
				char buf[256];
				sprintf(buf, "Got WM_KEYDOWN %d, wparam %x, lparam %x\n", message, wparam, lparam);
				OutputDebugString(buf);
				window->on_key_down((int) wparam, (lparam & (1 << 30)) != 0);
				break;
			}

		case WM_CHAR:
		case WM_SYSCHAR:
			{
				char buf[256];
				sprintf(buf, "Got WM_CHAR %d, wparam %x, lparam %x\n", message, wparam, lparam);
				OutputDebugString(buf);

				bool extended = (lparam & (1 << 24)) != 0;
				window->on_key_char((int) wparam, (lparam & (1 << 30)) != 0);
			}
			break;

		case WM_KEYUP:
		case WM_SYSKEYUP:
			window->on_key_up((int) wparam);
			break;
		}
		
	if (types & mt_resize)
	{
		switch (message)
		{
		case WM_ENTERSIZEMOVE:
			window->on_resize_starting();
			return 0;

		case WM_EXITSIZEMOVE:
			window->on_resize_finished();
			return 0;

		case WM_SIZE:
			{	
/*				if (window->m_got_border_sizes)
				{
					RECT rect;
					GetClientRect(hwnd, &rect);	
				
					window->on_resize(rect.right, rect.bottom);
				}
				else
				{
					RECT rect;
					GetClientRect(hwnd, &rect);
					window->m_window_border_size_x =
						window->width() - rect.right;
					window->m_window_border_size_y =
						window->height() - rect.bottom;
					window->m_got_border_sizes = true;
					
					window->resize(window->width(), window->height());
				}
*/			}
			return 0;

		case WM_SIZING:
			{
/*				if (window->m_got_border_sizes)
				{
					RECT* rect = (RECT*) lparam;
					int width = rect->right - rect->left - window->m_window_border_size_x;
					int height = rect->bottom - rect->top - window->m_window_border_size_y;
					
					window->on_resizing(width, height);

					bool resizing_bottom = true;
					bool resizing_right = true;
					
					if (	wparam == WMSZ_TOP
						||	wparam == WMSZ_TOPLEFT
						||	wparam == WMSZ_TOPRIGHT)
						resizing_bottom = false;
					
					if (	wparam == WMSZ_LEFT
						||	wparam == WMSZ_TOPLEFT
						||	wparam == WMSZ_BOTTOMLEFT)
						resizing_right = false;

					if (resizing_bottom)
						rect->bottom = rect->top + height + window->m_window_border_size_y;
					else
						rect->top = rect->bottom - height - window->m_window_border_size_y;

					if (resizing_right)
						rect->right = rect->left + width + window->m_window_border_size_x;
					else
						rect->left = rect->right - width - window->m_window_border_size_x;
				}
				return 0;
*/			}
		}
	}

	return DefWindowProc(hwnd, message, wparam, lparam);
}

void Window::create_window_class(HINSTANCE hinstance)
{
	s_class_name = "Generic class";
	
	WNDCLASSEXA win_class;
	
	win_class.cbSize = sizeof(WNDCLASSEX);
	win_class.style = CS_HREDRAW | CS_VREDRAW;
	win_class.lpfnWndProc = wnd_proc;
	win_class.cbClsExtra = 0;
	win_class.cbWndExtra = 0;
	win_class.hInstance = hinstance;
	win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	win_class.hCursor = LoadCursor(NULL,IDC_ARROW);
	win_class.hbrBackground = NULL;
	win_class.lpszMenuName = NULL;
	win_class.lpszClassName = s_class_name;
	win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
	
	if (!RegisterClassExA(&win_class))
	{
		Throw_Win32_Exception("RegisterClassEx");
	}

	s_window_class_created = true;
}

int Window::message_loop()
{
	while (true)
	{
		MSG msg;
	
		GetMessageA(&msg, 0, 0, 0);

		if (msg.message == WM_QUIT)
			return (int) msg.wParam;

		TranslateMessage(&msg);
		DispatchMessageA(&msg);
	}	
}			

Window::Window(
	HINSTANCE hinstance,
	int width,
	int height,
	const char* title,
	int timer_interval)
{
	if (!s_window_class_created)
	{
		create_window_class(hinstance);
	}

	m_x = 100;
	m_y = 100;
		
	m_hinstance = hinstance;

	RECT win_rect = { m_x, m_y, m_x + width, m_y + height };

	DWORD window_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

	AdjustWindowRectEx(
		&win_rect,
		window_style,
		FALSE,
		0);

	m_x = win_rect.left;
	m_y = win_rect.top;

	width = win_rect.right - win_rect.left;
	height = win_rect.bottom - win_rect.top;

	m_hwnd = CreateWindowEx(
		0,
		s_class_name,
		title,
		window_style,
		m_x, m_y,
		width, height,
		0,
		0,
		m_hinstance,
		0);

	if (m_hwnd == 0)
	{
		Throw_Win32_Exception("CreateWindowEx");
	}

	m_font = (HFONT) GetStockObject(SYSTEM_FIXED_FONT);
	
	m_width = width;
	m_height = height;

	m_timer_id = -1;
	m_timer_interval = timer_interval;
	if (m_timer_interval != 0)
	{
		ASSERT(m_timer_interval > 0);
		
		m_timer_id = s_timer_id_counter++;
		SetTimer(m_hwnd, m_timer_id, m_timer_interval, 0);
	}
	
	m_bg_color = GetSysColor(COLOR_WINDOW);
	m_fg_color = GetSysColor(COLOR_WINDOWTEXT);

	s_tracked_windows[m_hwnd] = this;

	LOGFONT log_font;
	
	CHOOSEFONT choose_font;
	memset(&choose_font, 0, sizeof(choose_font));
	choose_font.lStructSize = sizeof(choose_font);
	choose_font.hwndOwner = m_hwnd;
	choose_font.lpLogFont = &log_font;
//	choose_font.rgbColors = m_fg_color;
	choose_font.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY;

/*	if (ChooseFont(&choose_font) == TRUE)
	{
		m_font = CreateFontIndirect(&log_font);
	}
	else
	{*/
		memset(&log_font, 0, sizeof(log_font));
		log_font.lfHeight = -13;
		log_font.lfWeight = 400;
		strcpy(log_font.lfFaceName, "ProggyOpti");
		
		// m_font = (HFONT) GetStockObject(SYSTEM_FIXED_FONT);
		m_font = CreateFontIndirect(&log_font);
//	}

	if (m_font == NULL)
		m_font = (HFONT) GetStockObject(SYSTEM_FIXED_FONT);

	HDC tmp_hdc = GetDC(m_hwnd);
	HFONT old_font = (HFONT) SelectObject(tmp_hdc, m_font);
	
	TEXTMETRIC metrics;
	
	GetTextMetrics(tmp_hdc, &metrics);
	
	m_font_height = metrics.tmHeight;
	m_font_width = metrics.tmMaxCharWidth;

	SelectObject(tmp_hdc, old_font);	
	DeleteObject(tmp_hdc);
}

// Show the window. For C++-technical reasons this cannot be done in
// the constructor. (We need to trigger a WM_PAINT event inside the
// constructor but the virtual table for the derived class is not
// initialized at that time.)
void Window::show()
{
	MoveWindow(m_hwnd, m_x, m_y, width(), height(), TRUE);
	
	ShowWindow(m_hwnd, SW_SHOW);
	UpdateWindow(m_hwnd);
}

// Hide the window.
void Window::hide()
{
	ShowWindow(m_hwnd, SW_HIDE);
}

void Window::resize(int width, int height)
{
	// We know our border sizes, so adjust the size to include them.
/*	if (m_got_border_sizes)
	{
		MoveWindow(
			m_hwnd,
			m_x, m_y,
			width + m_window_border_size_x,
			height + m_window_border_size_y,
			TRUE);
	}
	else
	{
		// Postpone the resizing until we get our first WM_SIZE
		// event and knowledge of the border sizes.
		m_width = width;
		m_height = height;
	}
*/
}

